/*
 * Tic-Tac-Toe - Turn-based strategy game
 * Copyright 2008, 2009 Shayne Riley and Paul Maseberg
 *
 * Tic-Tac-Toe is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tic-Tac-Toe is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tic-Tac-Toe.  If not, see <http://www.gnu.org/licenses/>
 * or write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * To contact the authors on questions or issues regarding the license,
 * you may send an email to <shayner at gmail dot com>
 */

#include "BoardDisplay.h"
#include <iostream>
#include <iterator>
#include <list>
#ifdef _WIN32
#define _USE_MATH_DEFINES
#endif
#include <cmath>

using namespace std;

BoardDisplay::BoardDisplay(Graphics *gfx, Audio *aud,
        int leftX, int lowerY, int width, int height) :
    mGfx(gfx), mAud(aud),
    mLeftX(leftX), mLowerY(lowerY), mWidth(width), mHeight(height)
{
    // Position the buttons to align with the display.
    const int thirdWidth = mWidth / 3;
    const int thirdHeight = mHeight / 3;

    for (int i = 0; i < 3; ++i)
    {
        for (int j = 0; j < 3; ++j)
        {
            int buttonX = thirdWidth * j + leftX;
            int buttonY = thirdHeight * (2 - i) + lowerY;
            mSpots[i][j] = Button(buttonX, buttonY, thirdWidth, thirdHeight);
        }
    }

	mBoard = GameBoard();
}

BoardDisplay::~BoardDisplay()
{
}

GameBoard BoardDisplay::getBoard()
{
    return mBoard;
}

void BoardDisplay::updateBoard(GameBoard& board)
{
    mBoard = board;
}

void BoardDisplay::update()
{
    checkControls();
    drawGrid();
    drawMarkers();
    drawWinner();
}

void BoardDisplay::checkControls()
{
    if (mBoard.isGameOver())
    {
        return;
    }

    if (0 < mGfx->controls.size())
    {
        list<Graphics::Mouse>::const_iterator iter;

        for (iter = mGfx->controls.begin(); iter != mGfx->controls.end(); iter++)
        {
            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    mSpots[i][j].update(*iter);
                    if (mSpots[i][j].isClicked())
                    {
                        if (mBoard.makeMove(i, j))
                        {
                            Audio::Clip toPlay = Audio::CLIP_X;
                            // vary the effect because otherwise it is boring.
                            if (rand() % 2)
                            {
                                toPlay = Audio::CLIP_O;
                            }

                            mAud->play(toPlay);

                            if (!mBoard.isGameOver())
                            {
                                mBoard.makeMove();
                            }
                        }
                    }
                }
            }
        }
    }
}

void BoardDisplay::drawGrid()
{
    Graphics::Texture &textureLine = mGfx->textureLine;

    const int sixthWidth = mWidth / 6;
    const int sixthHeight = mHeight / 6;

    const float texHeightScale = (float) mHeight / (float) textureLine.height;
    const float texWidthScale = (float) mWidth / (float) mHeight * texHeightScale;

    // Set the color to black so that the grid shows as black.
    mGfx->setColor(0, 0, 0, 1.0f);

    for (int i = 1; i <= 2; ++i)
    {
        int texX = 2 * i * sixthWidth + mLeftX;
        int texY = 3 * sixthHeight + mLowerY;
        mGfx->drawTexture(&textureLine, texX, texY,
                (GLint) ((float) textureLine.width * texWidthScale),
                mHeight, 0.0f);
    }

    for (int j = 1; j <= 2; ++j)
    {
        int texX = 3 * sixthWidth + mLeftX;
        int texY = 2 * j * sixthHeight + mLowerY;
        mGfx->drawTexture(&textureLine, texX, texY,
                (GLint) ((float) textureLine.width * texWidthScale),
                mWidth, 90.0f);
    }
}

void BoardDisplay::drawMarkers()
{

    Graphics::Texture &textureO = mGfx->textureO;
    Graphics::Texture &textureX = mGfx->textureX;

    const int sixthWidth = mWidth / 6;
    const int sixthHeight = mHeight / 6;

    for (int i = 0; i < 3; ++i)
    {
        for (int j = 0; j < 3; ++j)
        {
            int texX = ((2 * j + 1) * sixthWidth) + mLeftX;
            int texY = -(((2 * i + 1) * sixthHeight)) + mLowerY + mHeight;
            GameBoard::Marker marker = mBoard.getMarker(i, j);
            if (marker == GameBoard::O)
            {
                mGfx->setColor(0, 0, 0.75f, 0.8f);
                mGfx->drawTexture(&textureO, texX, texY, sixthWidth * 2,
                        sixthHeight * 2, 0.0f);
            }
            else if (marker == GameBoard::X)
            {
                mGfx->setColor(0.6f, 0, 0, 0.8f);
                mGfx->drawTexture(&textureX, texX, texY, sixthWidth * 2,
                        sixthHeight * 2, 0.0f);
            }
            else if (mSpots[i][j].isHovering() || mSpots[i][j].isPressing())
            {
                mGfx->setColor(0.6f, 0, 0, 0.4f);
                mGfx->drawTexture(&textureX, texX, texY, sixthWidth * 2,
                        sixthHeight * 2, 0.0f);
            }
        }
    }
}

void BoardDisplay::drawWinner()
{
    int winLine = abs(mBoard.winner());

    // Don't need to do anything if there is no winner.
    if (winLine == 0)
    {
        return;
    }

    Graphics::Texture &textureLine = mGfx->textureLine;

    // Set the color to black so that the line shows as black.
    mGfx->setColor(0, 0, 0, 1.0f);

    const float texHeightScale = (float) mHeight / (float) textureLine.height;
    const float texWidthScale = (float) mWidth / (float) mHeight * texHeightScale;

    const int sixthWidth = mWidth / 6;
    const int sixthHeight = mHeight / 6;

    const float rad2deg = 180.0f * (float) M_1_PI; // Think this would be in cmath? No.

    GLint texX = mLeftX;
    GLint texY = mLowerY;
    GLint height = 0;
    GLint width = (GLint) ((float) textureLine.width * texWidthScale);
    float rotation = 0.0f;

    switch (winLine)
    {
    case 1: // column 0
    case 2: // column 1
    case 3: // column 2
        texX += (sixthWidth * (winLine - 1) * 2) + sixthWidth;
        texY += sixthHeight * 3;
        height = mHeight;
        break;
    case 4: // row 2
    case 5: // row 1
    case 6: // row 0
        texX += sixthWidth * 3;
        texY += (sixthHeight * (6 - winLine) * 2) + sixthHeight;
        height = mWidth;
        rotation = 90.0f;
        break;
    case 7: // upleft to downright diagonal
        texX += sixthWidth * 3;
        texY += sixthHeight * 3;
        rotation = atan((float) mWidth / (float) mHeight);
        rotation *= rad2deg;
        height = static_cast<GLint>(sqrt(static_cast<float>(mHeight * mHeight + mWidth * mWidth)));
        break;
    case 8: // the other diagonal
        texX += sixthWidth * 3;
        texY += sixthHeight * 3;
        rotation = atan((float) mWidth / (float) -mHeight);
        rotation *= rad2deg;
        height = static_cast<GLint>(sqrt(static_cast<float>(mHeight * mHeight + mWidth * mWidth)));
        break;
    default:
        cerr << "Apparently you won the game through other means." << endl;
    }

    mGfx->drawTexture(&textureLine, texX, texY, width, height, rotation);

}
